package cn.qianxun.meta.common.minio.util;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.qianxun.meta.common.core.utils.StringUtils;
import cn.qianxun.meta.common.minio.config.MinIoProperties;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.activation.MimetypesFileTypeMap;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * @Classname MinIoUtil
 * @Description TODO
 * @Date 2021/8/23 15:18
 * @Created by fuzhilin
 */
@Component
public class MinIoUtil {

    @Autowired
    private MinIoProperties minIoProperties;

    private static MinioClient minioClient;

    public static String intactUrl;

    private static String whiteTypeList;

    private static String bucketName = "socssac";

    public static String bucketNameCheck = "socssaccheck";//检查平台

    @Value("${spring.minio.intact-url}")
    public void setIntactUrl(String intactUrl) {
        MinIoUtil.intactUrl = intactUrl;
    }

    @Value("${spring.minio.white-type-list}")
    public void setWhiteTypeList(String whiteTypeList) {
        MinIoUtil.whiteTypeList = whiteTypeList;
    }

    private static Log log = Log.get("MinIoUtil");
    /**
     * bucket权限-只读
     */
    //   private static final String READ_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
    /**
     * bucket权限-只读
     */
    // private static final String WRITE_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
    /**
     * bucket权限-读写
     */

    /**
     * 初始化minio配置
     *
     * @param :
     * @return: void
     * @date : 2020/8/16 20:56
     */
    @PostConstruct
    public void init() {
        try {
            minioClient = MinioClient.builder().endpoint(minIoProperties.getUrl(), minIoProperties.getPort(), minIoProperties.getSecure()).credentials(minIoProperties.getAccessKey(), minIoProperties.getSecretKey()).build();
            String bucketName = StrUtil.blankToDefault(minIoProperties.getBucketName(), "socssac");
            boolean found =
                    minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!found) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            } else {
                log.info("Bucket already exists.");
            }
            //检查平台对应的上传文件夹
            boolean foundcheck = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketNameCheck).build());
            if (!foundcheck) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketNameCheck).build());
            } else {
                log.info("Bucket already exists.");
            }
            log.info("Minio Initialize........................successful");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("初始化minio配置异常: 【{}】", e.fillInStackTrace());
        }
    }

    /**
     * 判断 bucket是否存在
     *
     * @param bucketName: 桶名
     * @return: boolean
     * @date : 2020/8/16 20:53
     */
    @SneakyThrows(Exception.class)
    public static boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建 bucket
     *
     * @param bucketName: 桶名
     * @return: void
     * @date : 2020/8/16 20:53
     */
    @SneakyThrows(Exception.class)
    public static void createBucket(String bucketName) {
        boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (!isExist) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 获取全部bucket
     *
     * @param :
     * @return: java.util.List<io.minio.messages.Bucket>
     * @date : 2020/8/16 23:28
     */
    @SneakyThrows(Exception.class)
    public static List<Bucket> getAllBuckets() {
        return minioClient.listBuckets();
    }


    /**
     * 文件上传
     *
     * @param bucketName: 桶名
     * @param fileName:   文件名
     * @param stream:     文件流
     * @return: java.lang.String : 文件url地址
     * @date : 2020/8/16 23:40
     */
    @SneakyThrows(Exception.class)
    public static String upload(String bucketName, String fileName, InputStream stream) {
        fileName = filterString(fileName);
        String type = getFileType(fileName);
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", fileName));
            }
        }
        String contentType = Files.probeContentType(new File(fileName).toPath());
        if (StrUtil.isEmpty(contentType)) {
            contentType = new MimetypesFileTypeMap().getContentType(new File(fileName));
        }
        if (StrUtil.isNotEmpty(contentType)) {
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(stream, stream.available(), -1).contentType(contentType).build());
        } else {
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(stream, stream.available(), -1).build());
        }
        return getPresignedObjectUrl(bucketName, fileName);
    }

    /**
     * 文件夹和文件上传
     *
     * @param bucketName: 桶名
     * @param dirName:    文件夹名 多个文件夹的话以格式为usr/tmp,单个文件夹只传文件夹名称即可。
     * @param fileName:   文件名
     * @param stream:     文件流
     * @return: java.lang.String : 文件url地址
     * @date : 2020/8/16 23:40
     */
    @SneakyThrows(Exception.class)
    public static String upload(String bucketName, String dirName, String fileName, InputStream stream) {
        fileName = filterString(fileName);
        String type = getFileType(fileName);
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", fileName));
            }
        }
        String contentType = Files.probeContentType(new File(fileName).toPath());
        if (StrUtil.isEmpty(contentType)) {
            contentType = new MimetypesFileTypeMap().getContentType(new File(fileName));
        }
        if (StrUtil.isNotEmpty(dirName)) {
            fileName = dirName + "/" + fileName;
        }
        if (StrUtil.isNotEmpty(contentType)) {
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(stream, stream.available(), -1).contentType(contentType).build());
        } else {
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(stream, stream.available(), -1).build());
        }
        return getPresignedObjectUrl(bucketName, fileName);
    }


    @SneakyThrows(Exception.class)
    public static String uploadPDF(String bucketName, String fileName, InputStream stream) {
        fileName = filterString(fileName);
        String type = getFileType(fileName);
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", fileName));
            }
        }
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(stream, stream.available(), -1).contentType("application/pdf").build());
        return getPresignedObjectUrl(bucketName, fileName);
    }

    @SneakyThrows(Exception.class)
    public static String uploadPDF(String bucketName, String dirName, String fileName, InputStream stream) {
        fileName = filterString(fileName);
        String type = getFileType(fileName);
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", fileName));
            }
        }
        if (StrUtil.isNotEmpty(dirName)) {
            fileName = dirName + "/" + fileName;
        }
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(stream, stream.available(), -1).contentType("application/pdf").build());
        return getPresignedObjectUrl(bucketName, fileName);
    }

    /**
     * 文件上传
     *
     * @param bucketName: 桶名
     * @param file:       文件
     * @return: java.lang.String : 文件url地址
     * @date : 2020/8/16 23:40
     */
    @SneakyThrows(Exception.class)
    public static String upload(String bucketName, String uuid, MultipartFile file) {
        String type = getFileType(file.getOriginalFilename());
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", file.getOriginalFilename()));
            }
        }
        final InputStream is = file.getInputStream();
        final String fileName = uuid + filterString(file.getOriginalFilename());
        String objectName = "upload";
        PostPolicy policy = new PostPolicy(bucketName, fileName, ZonedDateTime.now().plusSeconds(30));
        minioClient.presignedPostPolicy(policy);
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(is, file.getSize(), -1).contentType(file.getContentType()).build());
        is.close();
        return getPresignedObjectUrl(bucketName, fileName);
    }

    /**
     * app上传文件专用(多文件夹)
     *
     * @param bucketName
     * @param fileName
     * @param folder
     * @param file
     * @return
     */
    @SneakyThrows(Exception.class)
    public static String uploadApp(String bucketName, String fileName, String folder, MultipartFile file) {
        fileName = filterString(fileName);
        String type = getFileType(fileName);
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", fileName));
            }
        }
        if (StringUtils.isNotEmpty(folder)) {
            fileName = folder + "/" + fileName;
        }
        //判断桶是否存在 不存在则创建
        createBucket(bucketName);
        final InputStream is = file.getInputStream();
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(is, file.getSize(), -1).contentType(file.getContentType()).build());
        is.close();
        return "/" + bucketName + "/" + fileName;

    }


    /**
     * 文件上传
     *
     * @param bucketName: 桶名
     * @param file:       文件
     * @return: java.lang.String : 文件url地址
     * @date : 2020/8/16 23:40
     */
    @SneakyThrows(Exception.class)
    public static String uploadFile(String bucketName, MultipartFile file) {
        String type = getFileType(file.getOriginalFilename());
        if (StrUtil.isNotEmpty(whiteTypeList)) {
            if (!whiteTypeList.contains(type + ",")) {
                throw new Exception(StrUtil.format("文件格式({})非法，不允许上传。 ", file.getOriginalFilename()));
            }
        }
        final InputStream is = file.getInputStream();
        String uuid = IdUtil.fastSimpleUUID();
        final String fileName = uuid + filterString(file.getOriginalFilename());
        // minioClient.putObject(bucketName, fileName, is, new PutObjectOptions(is.available(), -1));
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(is, is.available(), -1).contentType(file.getContentType()).build());
        is.close();
        return fileName;
    }

    /**
     * 删除文件
     *
     * @param bucketName: 桶名
     * @param fileName:   文件名
     * @return: void
     * @date : 2020/8/16 20:53
     */
    @SneakyThrows(Exception.class)
    public static void deleteFile(String bucketName, String fileName) {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
    }

    /**
     * 删除指定桶的多个文件对象,返回删除错误的对象列表，全部删除成功，返回空列表
     *
     * @param bucketName  存储桶名称
     * @param objectNames 含有要删除的多个object名称的迭代器对象
     * @return
     */
    @SneakyThrows(Exception.class)
    public static List<String> removeObjects(String bucketName, List<String> objectNames) {
        List<String> deleteErrorNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            List<DeleteObject> list = new LinkedList<>();
            objectNames.forEach(item -> list.add(new DeleteObject(item)));

            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrorNames.add(error.objectName());
            }
        }
        return deleteErrorNames;
    }


    /**
     * 下载文件
     *
     * @param bucketName: 桶名
     * @param fileName:   文件名
     * @param response:
     * @return: void
     * @date : 2020/8/17 0:34
     */
    @SneakyThrows(Exception.class)
    public static void downloadNew(String bucketName, String fileName, HttpServletResponse response) {
        fileName = filterString(fileName);
        boolean flag = isFileExsit(bucketName, fileName);
        if (flag) {
            ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
            InputStream in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.addHeader("Content-Length", "" + objectStat.length());
            response.setContentType("application/octet-stream;");
            response.setCharacterEncoding("UTF-8");
            IOUtils.copy(in, response.getOutputStream());
        } else {
            throw new ServerException("文件不存在");
        }
    }

    @SneakyThrows(Exception.class)
    public static InputStream downloadByFileName(String bucketName, String fileName) {
        fileName = filterString(fileName);
        boolean flag = isFileExsit(bucketName, fileName);
        if (flag) {
            ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
            return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } else {
            throw new ServerException("文件不存在");
        }
    }

    @SneakyThrows(Exception.class)
    public static void download(String bucketName, String fileName, HttpServletRequest request, HttpServletResponse response) {
        //DownloadObjectArgs downloadObjectArgs=new DownloadObjectArgs();
        // minioClient.downloadObject(downloadObjectArgs.builder().filename(fileName).object("upload").bucket(bucketName).build());
        boolean flag = isFileExsit(bucketName, fileName);
        if (flag) {
            ObjectStat objectStat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
            //fileName = fixStr(request, fileName);
            if (StrUtil.isNotEmpty(objectStat.contentType())) {
                response.setContentType(objectStat.contentType());
            } else {
                response.setContentType("application/octet-stream");
            }
            response.setHeader("Content-Disposition", "attachment;filename=" + fixStr(request, fileName));
            response.setHeader("X-Actual-Content-Length", String.valueOf(objectStat.length()));
            //cors(response, -1);
            OutputStream out = response.getOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            InputStream inputStream = null;
            boolean flagfa = false;
            try {
                inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
                flagfa = true;
                while ((len = inputStream.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }
            } catch (Exception e) {
                log.error("download file error.e=", e);
            } finally {
                if (flagfa) {
                    inputStream.close();
                }
                out.flush();
                out.close();
            }
        } else {
            throw new ServerException("文件不存在");
        }
    }

    /**
     * 清空某个bucket
     *
     * @param bucketName
     */
    public static void clearBucket(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            try {
                log.info("开始清空Minio桶名称为" + bucketName + "的所有文件");
                // 递归列举某个bucket下的所有文件，然后循环删除
                Iterable<Result<Item>> iterable = minioClient.listObjects(ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .recursive(true)
                        .build());
                for (Result<Item> itemResult : iterable) {
                    String objName = URLDecoder.decode(itemResult.get().objectName(), "utf-8");
                    log.info("minio删除文件为:" + objName);
                    minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objName).build());
                }
            } catch (Exception e) {
                log.error("清空Minio失败，桶名称为:" + bucketName, e.getMessage());
                e.printStackTrace();
            }
        }
    }

    public static void cors(HttpServletResponse response, long maxAge) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
        if (maxAge > 0) {
            response.setHeader("Access-Control-Max-Age", String.valueOf(maxAge));
            response.setHeader("Cache-Control", "max-age=" + maxAge);
        }
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Expose-Headers", "Cache-Control, Accept-Ranges, Content-Encoding, Content-Length, Content-Range, X-Actual-Content-Length, Content-Disposition");
    }

    private static String fixStr(HttpServletRequest request, String str) throws Exception {
        if (request.getHeader("User-Agent").toUpperCase().contains("MSIE") || request.getHeader("User-Agent").toUpperCase().contains("TRIDENT") || request.getHeader("User-Agent").toUpperCase().contains("EDGE")) {
            str = java.net.URLEncoder.encode(str, "UTF-8");
        } else {
            str = new String(str.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        }
        return str;
    }

    public static boolean checkSpecialChar(String str) throws PatternSyntaxException {
        // 清除掉所有特殊字符
        String regEx = ".*[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？\\\\]+.*";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(str);
        return m.matches();
    }

    /**
     * 处理斜杠的问题,其他特殊字符暂不处理，可能文件中已经存在了，兼容问题，后续如果有其他的特殊字符造成的问题可以在正则中进行增加处理
     * 原有政策为： ".*[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？\\\\]+.*";
     *
     * @param str
     * @return
     * @throws PatternSyntaxException
     */
    public static String filterString(String str) throws PatternSyntaxException {
        String regEx = "/";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(str);
        return m.replaceAll("_").replace(" ", "").trim();
    }

    public static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

//    /**
//     * 获取minio文件的下载地址
//     *
//     * @param bucketName: 桶名
//     * @param fileName:   文件名
//     * @return: java.lang.String
//     * @date : 2020/8/16 22:07
//     */
//    public static String getFileUrl(String bucketName, String fileName) {
//        String url = "";
//        try {
//            String s = minioClient.presignedGetObject(bucketName, fileName);
//            url = s.substring(intactUrl.length(), s.length());
//        } catch (Exception e) {
//            url = "";
//        }
//        return url;
//    }


    public static String getPresignedObjectUrl(String bucketName, String fileName) {
        String url = "";
        try {
            String s = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).expiry(3, TimeUnit.MINUTES).method(Method.GET).build());
            url = s.substring(intactUrl.length(), s.length());
        } catch (Exception e) {
            url = "";
        }
        return url;
    }

    public static String getPresignedObjectUrl(String fileName) {
        String url = "";
        try {
            String s = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).expiry(60, TimeUnit.MINUTES).method(Method.GET).build());
            url = s.substring(intactUrl.length(), s.length());
        } catch (Exception e) {
            url = "";
        }
        return url;
    }

    /**
     * 获取文件类型
     *
     * @param fileName
     * @return
     */
    public static String getFileType(String fileName) {
        if (StrUtil.isNotEmpty(fileName)) {
            String[] strArray = fileName.split("\\.");
            int suffixIndex = strArray.length - 1;
            return strArray[suffixIndex];
        }
        return "";

    }

    public static boolean isFileExsit(String bucketName, String objectName) {
        boolean flag = true;
        try {
            try {
                ObjectStat fileObejct = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            } catch (io.minio.errors.InternalException e) {
                e.printStackTrace();
                flag = false;
            }
        } catch (ErrorResponseException e) {
            flag = false;
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            flag = false;
            e.printStackTrace();
        } catch (InvalidBucketNameException e) {
            flag = false;
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            flag = false;
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            flag = false;
            e.printStackTrace();
        } catch (IOException e) {
            flag = false;
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            flag = false;
            e.printStackTrace();
        } catch (ServerException e) {
            flag = false;
            e.printStackTrace();
        } catch (XmlParserException e) {
            flag = false;
            e.printStackTrace();
        }
        return flag;
    }


}

